package utils

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"
)

// AuthState represents a single authentication state
type AuthState struct {
	Token     string            `json:"token"`
	TokenType string            `json:"token_type"` // "bearer", "api_key", "custom"
	ExpiresAt *time.Time        `json:"expires_at,omitempty"`
	Metadata  map[string]string `json:"metadata,omitempty"`
	CreatedAt time.Time         `json:"created_at"`
}

// AuthConfig represents authentication configuration from OpenAPI x-mcp-auth extension
type AuthConfig struct {
	Type        string            `json:"type"`                    // "login", "refresh", "logout"
	TokenPath   string            `json:"token_path"`              // JSON path to extract token
	TokenFormat string            `json:"token_format"`            // "bearer", "api_key", "custom"
	HeaderName  string            `json:"header_name,omitempty"`   // custom header name
	QueryParam  string            `json:"query_param,omitempty"`   // query parameter name
	TokenPrefix string            `json:"token_prefix,omitempty"`  // token prefix
	ExpiresPath string            `json:"expires_path,omitempty"`  // path to expiration info
	Requires    []string          `json:"requires,omitempty"`      // required auth types
	Metadata    map[string]string `json:"metadata,omitempty"`      // additional config
}

// AuthStateManager manages session-level authentication states
type AuthStateManager struct {
	states map[string]*AuthState // key: auth_type, value: AuthState
	mutex  sync.RWMutex
}

// SessionAuthData represents the complete authentication data for a session
type SessionAuthData struct {
	SessionID    string                `json:"session_id"`
	AuthStates   map[string]*AuthState `json:"auth_states"`
	LastActivity time.Time             `json:"last_activity"`
	CreatedAt    time.Time             `json:"created_at"`
}

// ToolAuthMetadata contains authentication metadata for tools
type ToolAuthMetadata struct {
	RequiredAuth []string    `json:"required_auth"`
	IsLoginTool  bool        `json:"is_login_tool"`
	AuthConfig   *AuthConfig `json:"auth_config,omitempty"`
}

// DynamicAuthConfig represents configuration options for dynamic authentication
type DynamicAuthConfig struct {
	Enabled                   bool          `json:"enabled"`
	MaxAuthStatesPerSession   int           `json:"max_auth_states_per_session"`
	TokenExpirationBuffer     time.Duration `json:"token_expiration_buffer"`
	CleanupInterval           time.Duration `json:"cleanup_interval"`
	LogLevel                  string        `json:"log_level"`
	EnableDebugLogs           bool          `json:"enable_debug_logs"`
}

// AuthError represents authentication-related errors
type AuthError struct {
	Type    string `json:"type"`
	Message string `json:"message"`
	Details string `json:"details,omitempty"`
}

// Error constants for different authentication error types
const (
	ErrTypeTokenExtraction = "token_extraction_failed"
	ErrTypeTokenExpired    = "token_expired"
	ErrTypeAuthRequired    = "authentication_required"
	ErrTypeConfigInvalid   = "invalid_auth_config"
	ErrTypeTokenInvalid    = "invalid_token_format"
)

// Error implements the error interface for AuthError
func (ae *AuthError) Error() string {
	if ae.Details != "" {
		return fmt.Sprintf("%s: %s (%s)", ae.Type, ae.Message, ae.Details)
	}
	return fmt.Sprintf("%s: %s", ae.Type, ae.Message)
}

// NewAuthError creates a new AuthError
func NewAuthError(errType, message, details string) *AuthError {
	return &AuthError{
		Type:    errType,
		Message: message,
		Details: details,
	}
}

// IsExpired checks if the authentication state has expired
func (as *AuthState) IsExpired() bool {
	if as.ExpiresAt == nil {
		return false
	}
	return time.Now().After(*as.ExpiresAt)
}

// Validate validates the AuthState structure
func (as *AuthState) Validate() error {
	if as.Token == "" {
		return NewAuthError(ErrTypeTokenInvalid, "token cannot be empty", "")
	}
	
	if as.TokenType == "" {
		return NewAuthError(ErrTypeTokenInvalid, "token_type cannot be empty", "")
	}
	
	validTokenTypes := map[string]bool{
		"bearer":  true,
		"api_key": true,
		"custom":  true,
	}
	
	if !validTokenTypes[as.TokenType] {
		return NewAuthError(ErrTypeTokenInvalid, 
			fmt.Sprintf("invalid token_type: %s", as.TokenType),
			"valid types are: bearer, api_key, custom")
	}
	
	return nil
}

// ToJSON serializes AuthState to JSON
func (as *AuthState) ToJSON() ([]byte, error) {
	return json.Marshal(as)
}

// FromJSON deserializes AuthState from JSON
func (as *AuthState) FromJSON(data []byte) error {
	if err := json.Unmarshal(data, as); err != nil {
		return NewAuthError(ErrTypeConfigInvalid, "failed to unmarshal AuthState", err.Error())
	}
	return as.Validate()
}

// Validate validates the AuthConfig structure
func (ac *AuthConfig) Validate() error {
	// Check if this is a "requires-only" config (only specifies authentication dependencies)
	isRequiresOnly := ac.Type == "" && ac.TokenPath == "" && ac.TokenFormat == "" && len(ac.Requires) >= 0
	
	// Check if this is a logout-only config (logout doesn't need token extraction)
	isLogoutOnly := ac.Type == "logout" && ac.TokenPath == "" && ac.TokenFormat == ""
	
	// If it's not a requires-only or logout-only config, validate the full structure
	if !isRequiresOnly && !isLogoutOnly {
		if ac.Type == "" {
			return NewAuthError(ErrTypeConfigInvalid, "type cannot be empty", "")
		}
		
		validTypes := map[string]bool{
			"login":   true,
			"refresh": true,
			"logout":  true,
		}
		
		if !validTypes[ac.Type] {
			return NewAuthError(ErrTypeConfigInvalid,
				fmt.Sprintf("invalid type: %s", ac.Type),
				"valid types are: login, refresh, logout")
		}
		
		if ac.Type == "login" && ac.TokenPath == "" {
			return NewAuthError(ErrTypeConfigInvalid, 
				"token_path is required for login type", "")
		}
		
		if ac.TokenFormat == "" {
			return NewAuthError(ErrTypeConfigInvalid, "token_format cannot be empty", "")
		}
		
		validFormats := map[string]bool{
			"bearer":  true,
			"api_key": true,
			"custom":  true,
		}
		
		if !validFormats[ac.TokenFormat] {
			return NewAuthError(ErrTypeConfigInvalid,
				fmt.Sprintf("invalid token_format: %s", ac.TokenFormat),
				"valid formats are: bearer, api_key, custom")
		}
	} else if ac.Type != "" {
		// Even for requires-only configs, if type is specified, it must be valid
		validTypes := map[string]bool{
			"login":   true,
			"refresh": true,
			"logout":  true,
		}
		
		if !validTypes[ac.Type] {
			return NewAuthError(ErrTypeConfigInvalid,
				fmt.Sprintf("invalid type: %s", ac.Type),
				"valid types are: login, refresh, logout")
		}
	}
	
	// Validate custom format requirements
	if ac.TokenFormat == "custom" && ac.HeaderName == "" && ac.QueryParam == "" {
		return NewAuthError(ErrTypeConfigInvalid,
			"custom token_format requires either header_name or query_param", "")
	}
	
	return nil
}

// ToJSON serializes AuthConfig to JSON
func (ac *AuthConfig) ToJSON() ([]byte, error) {
	return json.Marshal(ac)
}

// FromJSON deserializes AuthConfig from JSON
func (ac *AuthConfig) FromJSON(data []byte) error {
	if err := json.Unmarshal(data, ac); err != nil {
		return NewAuthError(ErrTypeConfigInvalid, "failed to unmarshal AuthConfig", err.Error())
	}
	return ac.Validate()
}

// Validate validates the DynamicAuthConfig structure
func (dac *DynamicAuthConfig) Validate() error {
	if dac.MaxAuthStatesPerSession <= 0 {
		return NewAuthError(ErrTypeConfigInvalid, 
			"max_auth_states_per_session must be positive", "")
	}
	
	if dac.TokenExpirationBuffer < 0 {
		return NewAuthError(ErrTypeConfigInvalid,
			"token_expiration_buffer cannot be negative", "")
	}
	
	if dac.CleanupInterval <= 0 {
		return NewAuthError(ErrTypeConfigInvalid,
			"cleanup_interval must be positive", "")
	}
	
	validLogLevels := map[string]bool{
		"debug": true,
		"info":  true,
		"warn":  true,
		"error": true,
	}
	
	if dac.LogLevel != "" && !validLogLevels[dac.LogLevel] {
		return NewAuthError(ErrTypeConfigInvalid,
			fmt.Sprintf("invalid log_level: %s", dac.LogLevel),
			"valid levels are: debug, info, warn, error")
	}
	
	return nil
}

// GetDefaultDynamicAuthConfig returns default configuration for dynamic authentication
func GetDefaultDynamicAuthConfig() *DynamicAuthConfig {
	return &DynamicAuthConfig{
		Enabled:                   true,
		MaxAuthStatesPerSession:   10,
		TokenExpirationBuffer:     5 * time.Minute,
		CleanupInterval:           10 * time.Minute,
		LogLevel:                  "info",
		EnableDebugLogs:           false,
	}
}